package ufpb.compiladores.lexico;

import java.io.File;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 * @author seminick
 */
public class XMLLoader {

    private File confFile = null;
    
    public XMLLoader(String confpath) {
    
        this(new File(confpath));
    }
    
    public XMLLoader(File confpath) {
    
        this.confFile = confpath;
    }
    
    public Enviroment createEnviroment() throws Exception {
        
        //Cria o enviroment vazio
        LexicalEnviroment env = new LexicalEnviroment();
        env.sensitive = true;
        
        Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
                .parse(this.confFile);
        
        Node fc = doc.getFirstChild();
        
        if (fc.getNodeName().equals("Configuration")) {
            
            NodeList root = fc.getChildNodes();
            final int len = root.getLength();
            for (int i = 0; i < len; i++) {

                Node cn = root.item(i);
                String cnn = cn.getNodeName();
                if (cnn.equals("Automaton")) {

                    parseAutomatoNode(cn, env);
                } else if(cnn.equals("Table")) {

                    parseTabelaNode(cn, env);
                }
            }
        }
        
        return env;
    }
    
    private void parseAutomatoNode(Node node, LexicalEnviroment env) throws Exception {
        
        //Verifica se o automato deve ser caso sensitivo
        Node senn = node.getAttributes().getNamedItem("sensitive");
        if (senn != null) {
            
            env.sensitive = Boolean.parseBoolean(senn.getTextContent());
        }
        
        //Guarda a localização da tag de trasações para ser processada por último
        Node trans = null;
        NodeList nl = node.getChildNodes();
        final int len = nl.getLength();
        for (int i = 0; i < len; i++) {
            
            Node childnode = nl.item(i);
            String childname = childnode.getNodeName();
            
            if (childname.equals("States")) {
        
                parseEstadosNode(childnode, env);
            } else if (childname.equals("Transitions")) {
                
                trans = childnode;
            }
        }
        
        if (trans != null) {
        
            parseTransictionNode(trans, env);
        }
    }
    
    private void parseTransictionNode(Node node, LexicalEnviroment env) throws Exception {
    
        Node como = node.getAttributes().getNamedItem("comentOpen");
        Node comc = node.getAttributes().getNamedItem("comentClose");
        if (como != null && comc != null) {
            
            env.como = como.getTextContent();
            env.comc = comc.getTextContent();
        }
        
        NodeList nodes = node.getChildNodes();
        final int len = nodes.getLength();
        for (int i = 0; i < len; i++) {
            
            Node tnode = nodes.item(i);
            NamedNodeMap attrs = tnode.getAttributes();
            
            if (tnode.getNodeName().equals("Transition")) {
            
                Node den = attrs.getNamedItem("from");
                Node ton = attrs.getNamedItem("to");
                Node charsn = attrs.getNamedItem("characters");

                if (den == null || ton == null) {

                    throw new Exception("Missing atribute in transition declaration");
                }

                String dens = den.getTextContent();
                String tons = ton.getTextContent();

                if (charsn != null) {

                    char[] cha = charsn.getTextContent().toCharArray();
                    for (char ch : cha) {

                        if (env.hasState(tons) && env.hasState(dens)) {

                            env.addTransiction(dens, ch, tons);
                        } else {

                            System.out.println("[WARNING] Ignoring invalid transition: from: "
                                    + dens + " to: " + tons + " with: " + ch
                                    + " cause a state is not valid");
                        }
                    }
                } else {
                    
                    if (env.hasState(tons) && env.hasState(dens)) {

                            env.addTransiction(dens, '\0', tons);
                        } else {

                            System.out.println("[WARNING] Ignoring invalid transition: from: "
                                    + dens + " to: " + tons + " with any character"
                                    + " cause a state is not valid");
                        }
                }
                
            } else if(tnode.getNodeType() != Node.TEXT_NODE) {
                
                System.out.println("[WARNING] Ignoring unkown tag: " + tnode.getNodeName());
            }
        }
    }
    
    private void parseEstadosNode(Node node, LexicalEnviroment env) throws Exception {
        
        Node inicial = node.getAttributes().getNamedItem("initial");
        if (inicial == null) {
            
            throw new Exception("An initial state must be especified");
        }
        
        NodeList childs = node.getChildNodes();
        final int len = childs.getLength();
        for (int i = 0; i < len; i++) {
        
            Node estadoN = childs.item(i);
            String name = estadoN.getNodeName();
            
            if (name.equals("State")) {
                
                Node finaln = estadoN.getAttributes().getNamedItem("final");
                boolean isfinal = false;
                if (finaln != null) {

                    isfinal = Boolean.parseBoolean(finaln.getTextContent());
                }
                
                String estado = estadoN.getTextContent();
                if (estado.equals("")) {
                    
                    throw new Exception("Each state must be a value");
                }
                
                env.addState(estado, isfinal);
            }
        }
        
        //Verifica se o estado inical foi declarado como estado
        if (!env.setInitialState(inicial.getTextContent())) {
            
            throw new Exception("Initial state must be declared as a state");
        }
    }
    
    private void parseTabelaNode(Node node, LexicalEnviroment env) throws Exception {
        
        NodeList nodes = node.getChildNodes();
        final int len = nodes.getLength();
        for (int i = 0; i < len; i++) {
            
            Node mapn = nodes.item(i);
            if (mapn.getNodeName().equals("Map")) {
                
                Node tokenn = mapn.getAttributes().getNamedItem("token");
                Node staten = mapn.getAttributes().getNamedItem("state");
                Node descn = mapn.getAttributes().getNamedItem("description");
                
                if ((tokenn == null && staten == null) || descn == null) {
                    
                    throw new Exception("[ERROR] Missing token or state and/or description attribute in Map tag");
                }
                
                if (tokenn != null) {
                
                    env.addDescription(tokenn.getTextContent(), descn.getTextContent());
                } else if (descn != null) {
                    
                    env.addStateDescription(staten.getTextContent(), descn.getTextContent());
                }
            } else if(mapn.getNodeType() != Node.TEXT_NODE) {
                
                System.out.println("[WARNING] Ignoring unkown tag " + mapn.getNodeName());
            }
        }
    }
    
    private static class LexicalEnviroment extends Enviroment {
        
        //Tabela com as transições do ambiente
        private HashMap<Map.Entry<String, Character>, String> transTable =
                new HashMap<Map.Entry<String, Character>, String>();
        
        //Tabela com os possíveis estados e especificando se é final
        private HashMap<String, Boolean> statesTable = new HashMap<String, Boolean>();      
        
        //Tabela com as descrições do token
        private HashMap<String, String> descTable = new HashMap<String, String>();
        
        //Tabela com as descrições dos estatos
        private HashMap<String, String> stateDescTable = new HashMap<String, String>();
        
        //Verifica se a comparação é sensitiva
        private boolean sensitive = false;
        
        //Estado inicial
        private String initialState = null;
        
        //Comentários
        private String como = null;
        private String comc = null;
        
        private void addTransiction(String state, char ch, String to) {
        
            //this.transTable.put(new LexicalEntry(state, ch), to);
            this.transTable.put(new AbstractMap.SimpleEntry(state, ch), to);
        }
        
        private void addState(String state, boolean isFinal) {
            
            if (this.sensitive) {
                
                state = state.toLowerCase();
            }
            
            this.statesTable.put(state, isFinal);
        }
        
        private void addDescription(String token, String desc) {
            
            if (this.sensitive) {
                
                token = token.toLowerCase();
            }
            
            this.descTable.put(token, desc);
        }
        
        private void addStateDescription(String token, String desc) {
            
            if (this.sensitive) {
                
                token = token.toLowerCase();
            }
            
            this.stateDescTable.put(token, desc);
        }
        
        private boolean setInitialState(String state) {
            
            if (!this.hasState(state)) {
                
                return false;
            }
            
            this.initialState = state;
            return true;
        }

        @Override
        public String transition(String state, char character) {
            
            //Verifica se tem transcicao com qualquer caracter
            String all = this.transTable.get(new AbstractMap.SimpleEntry(state, '\0'));
            if (all != null) {
                
                return all;
            }
            
            //return this.transTable.get(new LexicalEntry(state, character));
            return this.transTable.get(new AbstractMap.SimpleEntry(state, character));
        }

        @Override
        public boolean isFinalState(String state) {
            
            if (this.sensitive) {
                
                state = state.toLowerCase();
            }
            
            Boolean get = this.statesTable.get(state);
            
            return (get == null
                    ?false
                    :get.booleanValue());
        }

        @Override
        public boolean hasState(String state) {
            
            if (this.sensitive) {
                
                state  = state.toLowerCase();
            }
            
            return this.statesTable.containsKey(state);
        }

        @Override
        public String tokenDescription(String token) {
            
            if (this.sensitive) {
                
                token = token.toLowerCase();
            }
            
            return this.descTable.get(token);
        }

        @Override
        public String getInitialState() {
            
            return this.initialState;
        }

        @Override
        public boolean isInitialState(String state) {
            
            if (this.sensitive) {
                
                return state.equals(this.initialState);
            } else {
                
                return state.equalsIgnoreCase(this.initialState);
            }
        }

        @Override
        public String stateDescription(String state) {
            
            if (this.sensitive) {
                
                state = state.toLowerCase();
            }
            
            return this.stateDescTable.get(state);
        }

        @Override
        public String getComentOpen() {
            
            return this.como;
        }

        @Override
        public String getComentClose() {
            
            return this.comc;
        }
        
        private static class LexicalEntry implements Map.Entry<String, Character> {

            private String key = null;
            private Character value = null;
            
            public LexicalEntry(String s, Character c) {
                
                this.value = c;
                this.key = s;
            }
            
            @Override
            public String getKey() {
                
                return this.key;
            }

            @Override
            public Character getValue() {
                
                return this.value;
            }

            @Override
            public Character setValue(Character v) {

                this.value = v;
                return this.value;
            }
        
        }
    }
}
